import "./index.css";
import * as React from "react";
import Form from "../form";
import RequestUtils from "../../utils/RequestUtils";
import CommonUtils from "../../utils/CommonUtils";
import Pagination from "../pagination";
import Dropdown from "../dropdown";
import Collapse from "../collapse";
import Icons from "../icons";
import DialogBuilder from "../../utils/DialogBuilder";
import AlertUtils from "../../utils/AlertUtils";
import PropTypes from "prop-types"

/**
 * 数据表格组件
 * props: {
 *     fields: [{
 *         title: {string} 显示名称,
 *         key: {string} 对应json数据的列名称,
 *         formatter: {function|DataGrid.formatters} 单元格数据转换,
 *         searchable: {boolean} 是否可搜索,
 *         sortable: {boolean} 是否可排序,
 *         input: {InputElement} 单元格被编辑时的输入框，默认会根据formatter生成,
 *         searchInput: {React.Component} 搜索的编辑框，默认会根据formatter生成,
 *         defaultSearch: {boolean} 默认会使用此列进行搜索，即简易搜索中默认显示的列,
 *         thClassName: {string} th的样式类,
 *         className: {string} 样式类,
 *         style: {object} 样式
 *     }] 列配置,
 *     actions: [{
 *         title: {string} 显示标题,
 *         type: {DataGrid.actionType} 操作类型,
 *         className: {string} 附加样式类,
 *         style: {object} 附加样式,
 *         disabled: {boolean} 是否禁用,
 *         disabledOnNoneSelected: {boolean} 是否在未选中状态下禁用
 *     } | React.Component] 操作按钮,
 *     url: {string} 数据请求地址,
 *     page: {number} 初始分页，默认0,
 *     size: {number} 初始分页数量，默认20,
 *     pageParamName: {string} 分页参数名称，默认“page”,
 *     sizeParamName: {string} 分页数量名称，默认“size”,
 *     totalPropertyName: {string} 返回的数据中，统计数据总量的属性名称，默认“total”,
 *     dataPropertyName: {string} 返回数据中，数据列表的属性名称，默认“content”,
 *     additionalParam: {object} 附加参数，默认{},
 *     method: {string} 数据请求方式,
 *     dataHandler: {function} 数据请求结果处理,
 *     multipleSelect: {boolean} 是否可多选，默认是,
 *     multipleSort: {boolean} 是否可多列排序,
 *     onRowClick: {function} 行被单击回调，返回false不做处理,
 *     onRowDoubleClick: {function} 行被双击回调，返回false不做处理,
 *     onRowSelect: {function} 行被选中回调，返回false则不选中,
 *     onRowUnselect: {function} 行被取消选中回调，返回false则保持选中状态,
 *     onSelectedAll: {function} 全选事件,
 *     onSort: {function} 排序事件,
 *     sortParamHandler: {function} 排序参数处理
 * }
 */
export default class DataGrid extends React.Component{
    static propTypes = {
        fields: PropTypes.array,
        actions: PropTypes.array,
        url: PropTypes.string,
        page: PropTypes.number,
        size: PropTypes.number,
        pageParamName: PropTypes.string,
        sizeParamName: PropTypes.string,
        totalPropertyName: PropTypes.string,
        dataPropertyName: PropTypes.string,
        additionalParam: PropTypes.object,
        method: PropTypes.string,
        dataHandler: PropTypes.func,
        multipleSelect: PropTypes.bool,
        multipleSort: PropTypes.bool,
        onRowClick: PropTypes.func,
        onRowDoubleClick: PropTypes.func,
        onRowSelect: PropTypes.func,
        onRowUnselect: PropTypes.func,
        onSelectedAll: PropTypes.func,
        onSort: PropTypes.func,
        sortParamHandler: PropTypes.func,
    };
    /**
     * 列类型
     */
    static formatters = {
        LINE_NUMBER: function (field, row, fieldIndex, rowIndex) {
            return DataGrid.simpleCell(field, fieldIndex, rowIndex + 1);
        },
        TEXT: function(field, row, fieldIndex) {
            return DataGrid.simpleCell(field, fieldIndex,
                row[field.key] || <span className="text-muted">空</span>
            );
        },
        BOOLEAN: function(field, row, fieldIndex){
            return DataGrid.simpleCell(field, fieldIndex,
                row[field.key]?<Icons.check fill="#007bff" />:<Icons.x fill="#dc3545"/>
            );
        },
        DATE_TIME: function (field, row, fieldIndex) {
            return DataGrid.simpleCell(field, fieldIndex,
                CommonUtils.formatDate(new Date(row[field.key] || undefined))
            );
        },
        DATE: function (field, row, fieldIndex) {
            return DataGrid.simpleCell(field, fieldIndex,
                CommonUtils.formatDate("y-M-d", new Date(row[field.key] || undefined))
            );
        },
        TIME: function (field, row, fieldIndex) {
            return DataGrid.simpleCell(field, fieldIndex,
                CommonUtils.formatDate("H:m:s", new Date(row[field.key] || undefined))
            );
        },
    };

    /**
     * 简单单元格
     * @param field 列属性
     * @param fieldIndex 列下标
     * @param children 内容
     */
    static simpleCell(field, fieldIndex, children){
        if(fieldIndex === 0){
            return <th
                scope="row"
                key={fieldIndex}
                style={field.style}
                className={field.className}
            >
                {children
                ||
                <span className="text-muted">空</span>}
            </th>;
        }else{
            return <td
                key={fieldIndex}
                style={field.style}
                className={field.className}
                tabIndex={0}
            >
                {children
                ||
                <span className="text-muted">空</span>}
            </td>;
        }
    }

    /**
     * 操作类型
     */
    static actionType = {
        DELETE: "delete",
        REFRESH: "refresh",
        CONFIG: "config"
    };
    /**
     * 默认属性
     */
    static defaultProps = {
        tableClassName: "table-bordered table-hover table-striped",
        /**
         * 数据列
         */
        fields: [
            {title: "#", formatter: DataGrid.formatters.LINE_NUMBER},
            {title: "标题1", key: "field1", sortable: true},
            {title: "标题2", key: "field2", searchable: true},
            {title: "标题3", key: "field3", style: {color: "red"}, searchable: true},
            {title: "标题4", key: "field4", formatter: field=><td>{field + "--by render fun"}</td>},
        ],
        /**
         * 操作按钮
         */
        actions: [
            {title: "删除", type: DataGrid.actionType.DELETE, disabledOnNoneSelected: true},
            {title: "刷新", type: DataGrid.actionType.REFRESH},
            {title: "设置", type: DataGrid.actionType.CONFIG},
        ],
        //请求地址
        url: "/admin/system/user",
        //分页
        page: 0,
        //分页数量
        size: 20,
        //请求方法
        method: "GET",
        //分页参数名
        pageParamName: "page",
        //分页数量参数名
        sizeParamName: "size",
        //数据总量名称
        totalPropertyName: "total",
        //数据列表名称
        dataPropertyName: "content",
        //附加参数
        additionalParam: {},
        //请求结果处理
        dataHandler: null,
        //多列排序
        multipleSort: true,
        //是否可多选
        multipleSelect: true,
        //行被单击
        onRowClick: function (row, index){},
        //行被双击
        onRowDoubleClick: function (row, index){},
        //行被选中
        onRowSelect: function(row, index){},
        //行被取消选中
        onRowUnselect: function (row, index){},
        //全选
        onSelectedAll: function (rows) {},
        //排序事件
        onSort: function (fieldIndex, order) {},
        //排序参数处理
        sortParamHandler(fields) {
            let sortFields = fields.filter(field=>field.sortable && field.order);
            if(!sortFields.length) return {};
            return {
                sort: sortFields.map(sortField=>sortField.key + "," + sortField.order)
            }
        }
    };
    constructor(props){
        super(props);
        this.state = {fields: props.fields, actions: props.actions, size: props.size, page: props.page};
        this.selectAll = this.selectAll.bind(this);
        this.selectAllKeyDownHandler = this.selectAllKeyDownHandler.bind(this);
        this.onShiftDownHandler = this.onShiftDownHandler.bind(this);
        this.onShiftUpHandler = this.onShiftUpHandler.bind(this);
    }

    /**
     * 装载完成事件
     */
    componentDidMount() {
        this.loadData();
        CommonUtils.addKeyDownEventListener("Control a", this.selectAllKeyDownHandler);
        CommonUtils.addKeyDownEventListener("Shift", this.onShiftDownHandler);
        CommonUtils.addKeyUpEventListener("Shift", this.onShiftUpHandler);
    }

    componentWillUpdate(nextProps, nextState, nextContext) {
        if(this.props !== nextProps){
            this.setState({fields: nextProps.fields, actions: nextProps.actions, size: nextProps.size, page: nextProps.page}, ()=>{
                this.loadData();
            });
        }
    }

    /**
     * 即将卸载事件
     */
    componentWillUnmount() {
        CommonUtils.removeKeyDownEventListener("Control a", this.selectAllKeyDownHandler);
        CommonUtils.removeKeyDownEventListener("Shift", this.onShiftDownHandler);
        CommonUtils.removeKeyUpEventListener("Shift", this.onShiftUpHandler);
    }

    /**
     * 当Shift按下时，禁用选中文本
     * @param event
     */
    onShiftDownHandler(event){
        if(event.target.tagName === "TD" && CommonUtils.isChild("dataGrid", event.target)){
            let state = this.state;
            state.style = {userSelect: "none"};
            this.setState(state);
        }
    }

    /**
     * 当Shift弹开时，可以选中文本
     * @param event
     */
    onShiftUpHandler(event){
        if(event.target.tagName === "TD" && CommonUtils.isChild("dataGrid", event.target)){
            let state = this.state;
            state.style = {};
            this.setState(state);
        }
    }

    /**
     * 刷新数据
     */
    loadData(page, size){
        let data = Object.assign(this.props.sortParamHandler(this.state.fields), this.props.additionalParam, this.searchForm.getData());
        data[this.props.pageParamName] = page = (typeof page === "number")?page:(this.state.page || 0);
        data[this.props.sizeParamName] = size = size || this.state.size || 20;
        this.setState(Object.assign({}, this.state, {page: page, size: size}));
        RequestUtils.ajax(this.props.url, {
            method: this.props.method,
            params: data
        })
            .then(({data})=>{
                if (this.props.dataHandler instanceof Function) {
                    data = this.props.dataHandler(data);
                }
                this.refreshData(data);
            });
    }

    /**
     * 刷新数据
     * @param data
     */
    refreshData(data){
        let state = this.state;
        state.data = data || this.state.data;
        this.setState(state);
    }

    /**
     * 列显示设置
     */
    fieldDisplayConfig(){
        let form;
        new DialogBuilder(<Form ref={r=>form = r}>
            {this.state.fields.map((field,index)=>
                <div className="form-check form-check-inline w-25 mr-0" key={index}>
                    <label className="form-check-label">
                        <input className="form-check-input" type="checkbox" name={index} defaultChecked={field.display!==false}/>
                        {field.title}
                    </label>
                </div>
            )}
        </Form>)
            .title("自定义列表项")
            .btn("确定", ()=>{
                let result = form.getData();
                let state = this.state;
                Object.keys(result).forEach(index=>state.fields[index].display=result[index]);
                this.setState(state);
            })
            .removeOnClose();
    }

    /**
     * 删除行
     */
    delete(){
        let selectedRow = this.getSelectedRows();
        if(!selectedRow.length) {
            AlertUtils.warn("请选中一行");
            return;
        }
        new DialogBuilder(<span><Icons.alert className="mr-2" height={30} style={{fill: "red"}} />是否删除(共{selectedRow.length}条数据)?</span>)
            .smallModal()
            .title("警告")
            .btn("确认", ()=> {
                let successItems=[],failItems=[];
                Promise.all(selectedRow.map(row=>
                    RequestUtils.ajax(this.props.url + "/" + row.id, {
                        method: "delete"
                    })
                        .then(()=>successItems.push(row))
                        .catch(()=>failItems.push(row))
                )).then(()=>{
                    if(failItems.length){
                        AlertUtils.danger(failItems.length + "条数据删除失败");
                    }else{
                        AlertUtils.info("删除成功", 5000);
                    }
                })
            })
            .btn("取消", null, true, "btn-secondary")
            .removeOnClose();
    }

    /**
     * 生成对应列数据类型的input
     * @param field
     */
    static generateFieldInput(field){
        if(field.input) return field.input;
        switch (field.formatter) {
            case DataGrid.formatters.DATE:
                return <input type="date" name={field.key} key={field.key} className="form-control form-control-sm"/>;
            case DataGrid.formatters.DATE_TIME:
                return <input type="datetime" name={field.key} key={field.key} className="form-control form-control-sm"/>;
            case DataGrid.formatters.TIME:
                return <input type="time" name={field.key} key={field.key} className="form-control form-control-sm"/>;
            default:
                return <input type="text" name={field.key} key={field.key} className="form-control form-control-sm"/>;
        }
    }

    /**
     * 显示或隐藏高级搜索
     */
    toggleSeniorSearchBox(){
        let state = this.state;
        state.seniorSearch = !state.seniorSearch;
        this.setState(state);
    }

    /**
     * 获取被选中的行
     * @return {*}
     */
    getSelectedRows(){
        return this.state.data.content.filter(row=>row.selected);
    }

    /**
     * 选中全部
     */
    selectAll(){
        if(!this.props.multipleSelect) return;
        if(this.props.onSelectedAll() === false) return;
        let state = CommonUtils.clone(this.state);
        state.selectedSum = 0;
        state.data.content.forEach((row, index)=>{
            if(!row.selected){
                //回调和统计选中
                (row.selected = this.props.onRowSelect(row, index) !== false) && state.selectedSum++;
            }else{
                state.selectedSum++;
            }
        });
        this.setState(state);
    }

    /**
     * Control+a 全选快捷键处理
     */
    selectAllKeyDownHandler(event){
        if(event.target.tagName === "TD" && CommonUtils.isChild("dataGrid", event.target)) {
            this.selectAll();
            event.preventDefault();
        }
    }

    /**
     * 行被单击事件处理
     * @param rowIndex
     */
    onRowClickHandler(rowIndex){
        let state = CommonUtils.clone(this.state);
        let row = state.data.content[rowIndex];

        //回调事件处理
        if(this.props.onRowClick(row, rowIndex) === false) return;

        //选中处理
        if(this.props.multipleSelect && CommonUtils.keyStatus.Control){
            row.selected = !row.selected;
            if(row.selected) {
                //选中回调
                if(this.props.onRowSelect(row, rowIndex) !== false) {
                    row.selected = true;
                    //统计选中数量
                    state.selectedSum = (state.selectedSum || 0) + 1;
                    //如果选中设置第一个选中行，用于连续选择
                    if (this.firstSelectedRowIndex === undefined)
                        this.firstSelectedRowIndex = rowIndex;
                }
            }else{
                //统计选中数量
                state.selectedSum --;
                //如果行未选中且回调为false则保持选中状态
                !row.selected && this.props.onRowUnselect(row, rowIndex) === false && (row.selected = true);
            }
        }
        //连续选择
        else if(this.props.multipleSelect && CommonUtils.keyStatus.Shift){
            //计算连续选择的开始位置和结束位置
            let startSelectedRowIndex = this.firstSelectedRowIndex || 0;
            let endSelectedRowIndex = Math.max(startSelectedRowIndex, rowIndex);
            if(endSelectedRowIndex === startSelectedRowIndex) startSelectedRowIndex = rowIndex;
            state.selectedSum = 0;

            state.data.content.forEach((row, index)=>{
                if(index >= startSelectedRowIndex && index <= endSelectedRowIndex) {
                    //选中回调并赋值，和统计选中
                    (row.selected = this.props.onRowSelect(row, index) !== false) && state.selectedSum++;
                }else{
                    row.selected = this.props.onRowUnselect(row, index) === false;
                }
            });
        }
        //选中单行
        else{
            //回调取消选中并赋值
            state.data.content.forEach(row=>row.selected && (row.selected = this.props.onRowUnselect(row, rowIndex) === false));
            row.selected = this.props.onRowSelect(row, rowIndex) !== false;
            this.firstSelectedRowIndex = rowIndex;
            state.selectedSum = 1;
        }
        this.setState(state);
    }

    /**
     * 行被双击事件处理
     * @param rowIndex
     */
    onRowDoubleClickHandler(rowIndex){
        let row = this.state.data.content[rowIndex];
        if(this.props.onRowDoubleClick(row, rowIndex) === false) return;
    }

    /**
     * 排序操作
     */
    sort(fieldIndex){
        let fields = this.state.fields;
        let field = fields[fieldIndex];
        let order;
        if(field.order){
            order = field.order==="asc"?"desc":"";
        }else{
            order = "asc";
        }
        //回调
        if(this.props.onSort(fieldIndex, order) === false) return;

        //修改state
        if(this.props.multipleSort){
            fields[fieldIndex] = Object.assign({}, field, {order: order});
        }else {
            fields.forEach((field,index)=>{
                if(index === fieldIndex){
                    field.order = order;
                }else{
                    field.order = "";
                }
            });
        }
        this.setState({fields: fields},()=>this.loadData());
    }

    /**
     * 渲染
     */
    render() {
        //所有可见的列
        let fields = this.state.fields.filter(item=>item.display===undefined||item.display);

        //可搜索的列
        let searchableFields = fields.filter(field=>field.searchable);

        //当前所选择的搜索列
        let searchField = this.state.searchField || searchableFields.find(field=>field.defaultSearch) || (searchableFields.length && searchableFields[0]);
        let data = this.state.data || {};
        let tbody;

        //渲染数据
        if(data[this.props.dataPropertyName] && data[this.props.dataPropertyName].length){
            tbody = <tbody>
            {data[this.props.dataPropertyName].map((row,rowIndex)=>{
                return <tr
                    key={row.id||rowIndex}
                    onClick={this.onRowClickHandler.bind(this, rowIndex)}
                    onDoubleClick={this.onRowDoubleClickHandler.bind(this, rowIndex)}
                    className={row.selected?"table-primary":""}
                >
                    {fields.map((field, fieldIndex)=>
                        (field.formatter||DataGrid.formatters.TEXT)(field, row, fieldIndex, rowIndex))
                    }
                </tr>
            })}
            </tbody>;
        }else{
            tbody = <tbody>
            <tr>
                <td colSpan={fields.length} className="p-5 text-center text-muted">无数据</td>
            </tr>
            </tbody>
        }

        //简易搜索和高级搜索
        let simpleSearchBox, seniorSearchBox;
        if(searchField){
            simpleSearchBox =
                <div className="searchBox mb-2 float-none float-sm-right">
                    <div className="input-group input-group-sm">
                        <Dropdown className="input-group-prepend" btnContent={searchField.title} disabled={this.state.seniorSearch}>
                            {searchableFields.map(field=>
                                <span
                                    key={field.key}
                                    className="dropdown-item"
                                    style={{cursor: "pointer"}}
                                    onClick={()=>{let state=this.state; state.searchField = field; this.setState(state)}}
                                >
                                    {field.title}
                                </span>
                            )}
                        </Dropdown>
                        {React.cloneElement(DataGrid.generateFieldInput(searchField), {disabled: this.state.seniorSearch})}
                        <div className="input-group-append">
                            <button type="submit" className="btn btn-primary" disabled={this.state.seniorSearch}>搜索</button>
                            <button type="button" className="btn btn-secondary" onClick={this.toggleSeniorSearchBox.bind(this)}>高级</button>
                        </div>
                    </div>
                </div>;

            seniorSearchBox =
                <Collapse className="clearfix" expanded={this.state.seniorSearch}>
                    <div className="border-top pt-2 mb-2 d-block d-sm-flex flex-sm-wrap">
                        {searchableFields.map(field=>{
                            let input = field.searchInput;
                            if(!input){
                                return <div className="input-group input-group-sm mr-sm-2 mb-2 w-auto flex-sm-fill" key={field.key||field.type}>
                                    <div className="input-group-prepend">
                                        <span className="input-group-text">{field.title}</span>
                                    </div>
                                    {DataGrid.generateFieldInput(field)}
                                </div>
                            }
                            return input;
                        })}
                        {this.props.children}
                        <div className="d-block d-flex d-sm-inline">
                            <button type="submit" className="btn btn-sm btn-primary flex-fill">搜索</button>
                        </div>
                    </div>
                </Collapse>;
        }
        return <div className={"dataGrid " + (this.props.className||"")} style={Object.assign({}, this.state.style, this.props.style)}>
            <Form className="form d-block" ref={form=>this.searchForm=form} action={this.props.url} onSuccess={({data})=>this.refreshData(data)}>
                <div className="clearfix">
                    <div className="toolBox mb-2 float-none float-sm-left d-flex">
                        {this.state.actions.map((item,index)=>{
                            const actionBtnDefaultCls = "btn btn-secondary btn-sm mr-1 flex-fill ";
                            //如果是ReactElement则拷贝对象并修改class
                            if(React.isValidElement(item)){
                                return React.cloneElement(item, Object.assign({key: index}, item.props, {
                                    className: actionBtnDefaultCls + (item.props.className || ""),
                                    disabled: item.props.disabledonnoneselected && !this.state.selectedSum
                                }));
                            }
                            //操作按钮
                            switch (item.type) {
                                case DataGrid.actionType.CONFIG:
                                    return <button
                                        type="button"
                                        key={index}
                                        className={actionBtnDefaultCls + (item.className||"")}
                                        onClick={item.disabled?null:this.fieldDisplayConfig.bind(this)}
                                        disabled={item.disabled || (item.disabledOnNoneSelected && !this.state.selectedSum)}
                                    >
                                        {item.title || <Icons.gear style={{fill: "white"}}/>}
                                    </button>;
                                case DataGrid.actionType.REFRESH:
                                    return <button
                                        type="button"
                                        key={index}
                                        className={actionBtnDefaultCls + (item.className||"")}
                                        onClick={item.disabled?null:this.loadData.bind(this, null, null)}
                                        disabled={item.disabled || (item.disabledOnNoneSelected && !this.state.selectedSum)}
                                    >
                                        {item.title || <Icons.sync style={{fill: "white"}}/>}
                                    </button>;
                                case DataGrid.actionType.DELETE:
                                    return <button
                                        type="button"
                                        key={index}
                                        className={actionBtnDefaultCls + (item.className||"")}
                                        onClick={item.disabled?null:this.delete.bind(this)}
                                        disabled={item.disabled || (item.disabledOnNoneSelected && !this.state.selectedSum)}
                                    >
                                        {item.title || <Icons.trashcan style={{fill: "white"}}/>}
                                    </button>;
                                default:
                                    console.warn("未知的操作按钮", item);
                                    return null;
                            }
                        })}
                    </div>
                    <hr className="d-sm-none mb-2"/>
                    {simpleSearchBox}
                </div>
                {seniorSearchBox}
            </Form>
            <table className={"table " + (this.props.tableClassName||"")}>
                <caption className="align-middle">
                    <Pagination className="float-none float-md-left" pageTotal={Math.ceil(data.total/this.state.size)} currentPage={this.state.page} onGo={page=>this.loadData(page)}/>
                    <div className="float-none float-md-right">
                        <span>共{data[this.props.totalPropertyName]}条数据，{Math.ceil(data[this.props.totalPropertyName]/this.state.size)}页</span>
                        <label className="col-form-label ml-3">每页显示</label>
                        <select
                            className="custom-select custom-select-sm d-inline w-auto"
                            onChange={event => this.loadData.call(this,0, event.target.value)}
                            value={this.state.size}
                        >
                            <option>10</option>
                            <option>20</option>
                            <option>30</option>
                            <option>40</option>
                            <option>50</option>
                        </select>
                        <span>条</span>
                    </div>
                </caption>
                <thead className="thead-light">
                <tr>
                    {this.state.fields.map((item, index)=>{
                        if(item.display === false) return null;
                        let className = item.thClassName || "";
                        if(item.sortable) className += " sortable";
                        if(item.order){
                            className += item.order==="asc"?" sorting-asc":" sorting-desc";
                        }
                        return <th scope="col" className={className} key={item.key||index} onClick={item.sortable?this.sort.bind(this,index):null}>{item.title}</th>
                    })}
                </tr>
                </thead>
                {tbody}
            </table>
        </div>
    }
}
